package authservice

import (
	"context"
	"fmt"
	"gitee.com/go-mid/auth/internal/component"
	"gitee.com/go-mid/auth/internal/dao"
	"gitee.com/go-mid/auth/internal/util"
	"gitee.com/go-mid/booter/web"
	"gitee.com/go-mid/infra/xcache/xredis"
	"gitee.com/go-mid/infra/xlog"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"time"
)

type GetTokenReq struct {
	UserId     int64              `json:"user_id"`
	UserPoolID int64              `json:"user_pool_id"`
	Appid      int64              `json:"appid"`
	AuthType   web.EnumAuthType   `json:"auth_type"`
	ClientInfo web.XFRMClientInfo `json:"client_info"`
}
type GetTokenRes struct {
	AccessToken        string `json:"access_token"`
	AccessTokenExpire  int64  `json:"access_token_expire"`
	RefreshToken       string `json:"refresh_token"`
	RefreshTokenExpire int64  `json:"refresh_token_expire"`
}

func (m *AuthServiceImpl) GetToken(ctx context.Context, req *GetTokenReq) (r *GetTokenRes, err error) {
	fun := "AuthServiceImpl.GetToken -->"
	if req.UserId <= 0 {
		xlog.Warnf(ctx, "%s invalid userid : req: %v ", fun, req)
		return nil, status.Error(codes.Unauthenticated, "invalid userid")
	}
	//同一个用户，同时只能放过一个请求来获取token
	util.DistributeLockExec(ctx, fmt.Sprintf("gettoken:%d-%d", req.UserId, req.UserPoolID), time.Second*15, time.Second*5, func() error {
		switch req.AuthType {
		case web.EnumAuthType_RefreshNew:
			r, err = m.getTokenForceRefresh(ctx, req)
		default:
			err = status.Error(codes.InvalidArgument, fmt.Sprintf("not support authtype: %d", req.AuthType))
		}
		return nil
	})
	return
}

type AuthReq struct {
	AccessToken string `json:"access_token"`
}
type AuthRes struct {
	AuthInfo   web.XFRMAuthInfo   `json:"auth_info"`
	ClientInfo web.XFRMClientInfo `json:"client_info"`
}

// Auth ...
func (m *AuthServiceImpl) Auth(ctx context.Context, req *AuthReq) (r *AuthRes, err error) {
	fun := "AuthServiceImpl.Auth -->"
	authToken, err := m.getTokenCache(ctx, req.AccessToken)
	//缓存中找到token，则直接返回缓存中的token信息
	if authToken != nil {
		clientInfo, _ := web.ParseXFRMClientInfo(ctx, authToken.ClientInfo)
		return &AuthRes{
			AuthInfo: web.XFRMAuthInfo{
				UserId:     authToken.UserID,
				AuthType:   web.EnumAuthType(authToken.AuthType),
				Appid:      int(authToken.Appid),
				UserPoolID: authToken.UserPoolID,
				Principal:  "",
			},
			ClientInfo: clientInfo,
		}, nil
	}
	isRedisNil := err != nil && err.Error() == xredis.RedisNil
	//缓存中未找到token，分两种情况：
	//情况一 缓存中是nil。从数据库中装载，如果db中没有，则认证失败，否则加入到缓存
	//情况二 获取缓存失败。降级通过token服务获取认证信息,合法就放过
	now := time.Now().Unix()
	//先通过token服务初步校验 token是合法的，如果不合法 直接快速结束
	var atkPayload web.XFRMAuthInfo
	ok, err := m.verifyAccessToken(ctx, getAtkOpenKey(ctx), req.AccessToken, &atkPayload)
	if err != nil {
		xlog.Warnf(ctx, "%s atk from db verify token  error: token: req: %v, err: %v", fun, req, err)
		return nil, status.Error(codes.Unauthenticated, "登录失败，请重新登录")
	}
	if !ok {
		xlog.Warnf(ctx, "%s atk from db verify token  fail: token: req: %v, err: %v", fun, req, err)
		return nil, status.Error(codes.Unauthenticated, "登录失败，请重新登录")
	}
	//处理情况2
	if !isRedisNil {
		clientInfo, _ := web.ParseXFRMClientInfo(ctx, authToken.ClientInfo)
		return &AuthRes{
			AuthInfo: web.XFRMAuthInfo{
				UserId:   atkPayload.UserId,
				AuthType: atkPayload.AuthType,
				Appid:    atkPayload.Appid,
			},
			ClientInfo: clientInfo,
		}, nil
	}
	//处理情况1
	authTokenFromDB, err := dao.GetAuthTokenByToken(ctx, component.XDBAuth, req.AccessToken)
	if err != nil {
		xlog.Warnf(ctx, "%s atk from db error: token: req: %v, err: %v", fun, req, err)
		return nil, status.Error(codes.Unauthenticated, "登录过期，请重新登录")
	}
	if authTokenFromDB == nil || authTokenFromDB.AccessTokenExpire < now {
		xlog.Warnf(ctx, "%s atk from db expired: token: req: %v, err: %v", fun, req, err)
		return nil, status.Error(codes.Unauthenticated, "登录过期，请重新登录")
	}
	//将token 放入到缓存
	m.setTokenCache(ctx, authTokenFromDB)
	clientInfo, _ := web.ParseXFRMClientInfo(ctx, authTokenFromDB.ClientInfo)
	return &AuthRes{
		AuthInfo: web.XFRMAuthInfo{
			UserId:     authTokenFromDB.UserID,
			AuthType:   web.EnumAuthType(authTokenFromDB.AuthType),
			Appid:      int(authTokenFromDB.Appid),
			UserPoolID: authTokenFromDB.UserPoolID,
		},
		ClientInfo: clientInfo,
	}, nil

}
